package org.xtext.runtime.interpreter;

import is.lill.acre.conversation.Conversation;
import is.lill.acre.conversation.ConversationManager;
import is.lill.acre.conversation.ConversationStatus;
import is.lill.acre.event.ACREEvent;
import is.lill.acre.event.AmbiguousMessageEvent;
import is.lill.acre.event.ConversationAdvancedEvent;
import is.lill.acre.event.ConversationCancelConfirmEvent;
import is.lill.acre.event.ConversationCancelFailEvent;
import is.lill.acre.event.ConversationCancelRequestEvent;
import is.lill.acre.event.ConversationEndedEvent;
import is.lill.acre.event.ConversationFailedEvent;
import is.lill.acre.event.ConversationStartedEvent;
import is.lill.acre.event.ConversationTimeoutEvent;
import is.lill.acre.event.IConversationEvent;
import is.lill.acre.event.UnmatchedMessageEvent;
import is.lill.acre.logic.Utilities;
import is.lill.acre.message.ACREAgentIdentifier;
import is.lill.acre.message.ACREMessage;
import is.lill.acre.message.IACREAgentIdentifier;
import is.lill.acre.message.IACREMessage;
import is.lill.acre.protocol.Protocol;
import is.lill.acre.protocol.ProtocolDescriptor;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.xtext.runtime.acre.ACREService;
import org.xtext.runtime.cartago.ArtifactStore;
import org.xtext.runtime.cartago.ICartagoPlanStep;
import org.xtext.runtime.control.IExecutable;
import org.xtext.runtime.eis.EISService;
import org.xtext.runtime.formula.ACREASTRAEvent;
import org.xtext.runtime.formula.CartagoPropertyEvent;
import org.xtext.runtime.formula.Event;
import org.xtext.runtime.formula.IFormula;
import org.xtext.runtime.formula.MentalEvent;
import org.xtext.runtime.formula.MessageEvent;
import org.xtext.runtime.formula.Predicate;
import org.xtext.runtime.formula.TRActive;
import org.xtext.runtime.library.LibraryException;
import org.xtext.runtime.library.LibraryWrapper;
import org.xtext.runtime.messaging.AstraMessage;
import org.xtext.runtime.messaging.MessageService;
import org.xtext.runtime.plan.IPlanStep;
import org.xtext.runtime.plan.PlanStack;
import org.xtext.runtime.teleoreactive.TRFunction;
import org.xtext.runtime.term.ITerm;
import org.xtext.runtime.term.Term;
import org.xtext.runtime.util.Bindings;
import org.xtext.runtime.util.EvaluateTermsVisitor;
import org.xtext.runtime.util.IQueryable;
import org.xtext.runtime.util.IReasoner;
import org.xtext.runtime.util.ResolutionBasedReasoner;
import org.xtext.runtime.util.Unifier;
import cartago.ArtifactObsProperty;
import cartago.CartagoEvent;
import cartago.CartagoException;
import cartago.CartagoService;
import cartago.ICartagoListener;
import cartago.ICartagoSession;
import cartago.Tuple;
import cartago.events.ActionFailedEvent;
import cartago.events.ActionSucceededEvent;
import cartago.events.ArtifactObsEvent;
import cartago.events.FocusSucceededEvent;
import cartago.events.FocussedArtifactDisposedEvent;
import cartago.events.JoinWSPSucceededEvent;
import cartago.events.QuitWSPSucceededEvent;
import cartago.events.StopFocusSucceededEvent;
import cartago.security.AgentIdCredential;
import eis.exceptions.NoEnvironmentException;
import eis.exceptions.PerceiveException;
import eis.iilang.Identifier;
import eis.iilang.Numeral;
import eis.iilang.Parameter;
import eis.iilang.Percept;

/**
 * Core interpreter implementation. This is the main ASTRA Interpreter
 * @author rem
 *
 */
public class Agent implements IQueryable, IExecutable, ICartagoListener, Observer {
	/**
	 * Static method to store the default message service to be used by agents
	 */
    private static MessageService defaultMessageService;
    
    /**
     * The actual message service used by the agent
     */
    private MessageService messageService;

    /**
     * Set the default message service (used when the launcher installs a message service)
     * 
     * @param messageService
     */
    public static void defaultMessageService( MessageService messageService ) {
        defaultMessageService = messageService;
    }

    /**
     * Inner class to represent notifications of updates to plan stacks (e.g. when
     * a threaded activity completes).
     * 
     * @author rem
     */
    protected class Notification {
        public Notification( PlanStack planStack, boolean success ) {
            this.planStack = planStack;
            this.success = success;
        }

        PlanStack planStack;
        boolean success;
    }

    // GENERAL FIELDS
    private String name;
    private long iteration = 0;
    private Profiler profiler;
    private boolean terminate;

    // EIS FIELDS
    private EISService eisService;

    // CARTAGO FIELDS
    private ICartagoSession session;
    private List<CartagoEvent> cartagoEvents = new LinkedList<CartagoEvent>();
    private Map<Long, PlanStack> operationRegister = new HashMap<Long, PlanStack>();
    private ArtifactStore artifactStore = new ArtifactStore();


    // ACRE FIELDS
    private ConversationManager conversationManager;
    private List<IACREMessage> problems;
    private IACREAgentIdentifier acreId;
    private List<ACREEvent> acreEvents = Collections.synchronizedList(new LinkedList<ACREEvent>());

    // ASTRA Mental State
    private Map<String, List<IFormula>> beliefs = new HashMap<String, List<IFormula>>();
    private Map<String, LibraryWrapper> libraries = new HashMap<String, LibraryWrapper>();
    private Map<String, List<IFormula>> percepts = new HashMap<String, List<IFormula>>();
    private List<Predicate> perceptList = new LinkedList<Predicate>();
    private List<Predicate> addBeliefs = new LinkedList<Predicate>();
    private List<Predicate> dropBeliefs = new LinkedList<Predicate>();
    private List<AstraMessage> inbox = Collections.synchronizedList( new LinkedList<AstraMessage>() );

    protected Queue<Notification> notifications = new ConcurrentLinkedQueue<Notification>();
    private Set<String> tokens = new HashSet<String>();
    private Map<String, LinkedList<PlanStack>> lockQueueMap = new HashMap<String, LinkedList<PlanStack>>();
    private Map<String, PlanStack> lockMap = new HashMap<String, PlanStack>();

    private List<String> actionLog = new LinkedList<String>();
    private Queue<Event> events = new LinkedList<Event>();
    private IReasoner reasoner = new ResolutionBasedReasoner();
    private List<PlanStack> intentions = new LinkedList<PlanStack>();
    private Set<String> eventFilter = new HashSet<String>();
    private List<ASTRAClass> linearization;

    private List<TRFunction> activeFunctions = new LinkedList<TRFunction>();

	private Event event;

    private static Logger log = Logger.getLogger( Agent.class.getName() );
    
	// Unit Testing Members
    private MentalEvent runningEvent; 
    private boolean isRunningEvent = false;
    private boolean isEventFailed = false;
    
    private String runningPlan; 
    private boolean isRunningPlan = false;
    private boolean isPlanFailed = false;
    
    private Rule runningRule; 
    private boolean isRunningRule = false;
    private boolean isRuleFailed = false;
    
    static {
        log.setLevel( Level.INFO );
    }

    /**
     * Main constructor - creates an agent with a given name. This should not be
     * invoked directly but only through the ASTRAClass.newInstance method.
     * 
     * @param name the name of the agent being created
     */
    public Agent( String name ) {
        this.name = name;

        reasoner.addSource( this );

        // Install default Message Service...
        messageService = defaultMessageService;
        if ( defaultMessageService == null ) {
            log.info( "No Default Message Service for agent: " + name );
        }
        // profiler = new FileProfiler(name);
    }

    /**
     * Initialise CARTAGO support for agent...
     */
    public void startCartago() {
        try {
            session = CartagoService.startSession( "default", new AgentIdCredential( name ), this );
            log.info( "[" + name + "] Cartago Session created" );
        }
        catch ( CartagoException e1 ) {
            System.err.println( "[" + name + "] Problem creating Cartago Session" );
            e1.printStackTrace();
        }

    }

    /**
     * Initialise ACRE support for agent...
     */
    public void startACRE() {
        problems = new ArrayList<IACREMessage>();

        acreId = new ACREAgentIdentifier( name() );

        conversationManager = new ConversationManager();
        conversationManager.setOwner( acreId );
        conversationManager.setProtocolManager( ACREService.manager );
        conversationManager.addObserver( this );

        // NOTE: May need to observe protocol manager too...
        
        // for now, syntax of 'acre' and 'astra' is the same
        Utilities.addTermParser( "astra", Utilities.getTermParser( "acre" ) );
        Utilities.addTermFormatter( "astra", Utilities.getTermFormatter( "acre" ) );

    }

    public String name() {
        return name;
    }

    @Override
    public List<IFormula> getMatchingFormulae( IFormula formula ) {
        if ( Predicate.class.isInstance( formula ) ) {
            Predicate predicate = (Predicate) formula;
            List<IFormula> list = new LinkedList<IFormula>();
            List<IFormula> l = beliefs.get( predicate.signature() );
            if ( l != null )
                list.addAll( l );
            l = percepts.get( predicate.signature() );
            if ( l != null )
                list.addAll( l );
            return list;
        }
        else if ( TRActive.class.isInstance( formula ) ) {
            List<IFormula> list = new LinkedList<IFormula>();
            TRActive active = (TRActive) formula;
            for ( TRFunction function : activeFunctions ) {
                if ( function.identifier().signature().equals( ( (Predicate) active.formula() ).signature() ) ) {
                    list.add( new TRActive( function.identifier().copy() ) );
                }
            }
            return list;
        }
        return null;
    }

    public boolean startFunction( Predicate fn ) {
        TRFunction function = getFunction( fn );
        synchronized ( activeFunctions ) {
            if ( function != null ) {
                Bindings bindings = Unifier.unify( fn, function.identifier() );

                if ( bindings != null ) {
                    function = function.apply( bindings );
                    if ( !activeFunctions.contains( function ) ) {
                        activeFunctions.add( function );
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private TRFunction getFunction( Predicate fn ) {
        for ( ASTRAClass clazz : linearization ) {
            TRFunction function = clazz.getFunction( fn );
            if ( function != null )
                return function;
        }
        return null;
    }

    public boolean stopFunction( Predicate predicate ) {
        synchronized ( activeFunctions ) {
            for ( int i = 0; i < activeFunctions.size(); i++ ) {
                if ( activeFunctions.get( i ).identifier().matches( predicate ) ) {
                    activeFunctions.remove( i );
                    return true;
                }
            }
        }
        return false;
    }

    public void addBelief( Predicate belief ) {
        addBeliefs.add( belief );
    }

    public void addPercept( Predicate belief ) {
        perceptList.add( belief );
    }

    public void removeBelief( Predicate belief ) {
        dropBeliefs.add( belief );
    }

    private synchronized void updateBeliefs() {
        while ( !addBeliefs.isEmpty() ) {
            Predicate belief = addBeliefs.remove( 0 );
            String sig = belief.signature();
            List<IFormula> list = beliefs.get( sig );
            if ( list == null ) {
                list = new LinkedList<IFormula>();
                beliefs.put( sig, list );
            }

            if ( !list.contains( belief ) ) {
                list.add( belief );
                addEvent( new MentalEvent( MentalEvent.ADDITION, belief ) );
            }
        }

        while ( !dropBeliefs.isEmpty() ) {
            Predicate belief = dropBeliefs.remove( 0 );
            String sig = belief.signature();
            List<IFormula> list = beliefs.get( sig );
            if ( list != null ) {
                if ( list.remove( belief ) ) {
                    addEvent( new MentalEvent( MentalEvent.REMOVAL, belief ) );
                }
            }
        }
    }

    private void addPerception( List<Predicate> newPerceptList, Predicate belief ) {
        String sig = belief.signature();
        List<IFormula> list = percepts.get( sig );
        if ( list == null ) {
            list = new LinkedList<IFormula>();
            percepts.put( sig, list );
        }

        if ( !list.contains( belief ) ) {
            list.add( belief );
            newPerceptList.add( belief );
        }

        // Now decide whether or not an event should be generated...
        if ( !perceptList.remove( belief ) ) {
            addEvent( new MentalEvent( MentalEvent.ADDITION, belief ) );
        }
    }

    private synchronized void senseEnvironment() {
        percepts.clear();
        List<Predicate> newPerceptList = new LinkedList<Predicate>();

        // special case for eis environments...
        if ( eisService != null ) {
        	senseEIS(newPerceptList);
        }

        if (conversationManager != null) { // eh?
        	senseCARTAGO(newPerceptList);
        }
        if ( conversationManager != null ) {
            senseACRE(newPerceptList);
        }

        for ( LibraryWrapper library : libraries.values() ) {
            for ( Predicate belief : library.senseEnvironment() ) {
                addPerception( newPerceptList, belief );
            }
        }

        while ( !perceptList.isEmpty() ) {
            addEvent( new MentalEvent( MentalEvent.REMOVAL, perceptList.remove( 0 ) ) );
        }

        perceptList = newPerceptList;
    }

    private void senseCARTAGO(List<Predicate> newPerceptList) {
//        addPerception( newPerceptList, new Predicate( "currentWorkspace", new ITerm[] { Term.newInstance( currentWorkspace ) } ) );
	}

	private void senseEIS(List<Predicate> newPerceptList) {
        addPerception( newPerceptList, new Predicate( "eisState", new ITerm[] { Term.newInstance( eisService.getEnvironmentState() ) } ) );

        if ( eisService.isStartSupported() ) {
            addPerception( newPerceptList, new Predicate( "eisSupports", new ITerm[] { Term.newInstance( "start" ) } ) );
        }

        if ( eisService.hasEnvironmentEvent() ) {
            addPerception( newPerceptList, eisService.getNextEnvironmentEvent().toPredicate() );
        }

        // Agent Specific Percepts
        // for (String entity : associations) {
        // list.add(new Predicate("associatedWithEntity", new ITerm[] {
        // Term.newInstance(entity) }));
        // }

        try {
            Collection<Collection<Percept>> percepts = eisService.getPercepts( this ).values();
            for ( Collection<Percept> lst : percepts ) {
                for ( Percept percept : lst ) {
                    addPerception( newPerceptList, convertPercept( percept ) );
                }
            }
        }
        catch ( PerceiveException e ) {
            e.printStackTrace();
        }
        catch ( NoEnvironmentException e ) {
            e.printStackTrace();
        }

        List<Percept> percepts = eisService.getAgentEvents( this );
        while ( percepts != null && !percepts.isEmpty() ) {
            addPerception( newPerceptList, convertPercept( percepts.remove( 0 ) ) );
        }
	}

	public Predicate convertPercept( Percept percept ) {
        if ( percept == null )
            return null;
        List<Parameter> parameters = percept.getParameters();
        ITerm[] terms = new ITerm[ parameters.size() ];
        for ( int i = 0; i < parameters.size(); i++ ) {
            Parameter parameter = parameters.get( i );
            if ( Numeral.class.isInstance( parameter ) ) {
                terms[ i ] = Term.newInstance( ( (Numeral) parameter ).getValue() );
            }
            else {
                terms[ i ] = Term.newInstance( ( (Identifier) parameter ).getValue() );
            }
        }

        return new Predicate( percept.getName(), terms );
    }

    public long getNow() {
        return System.nanoTime() / 1000000;
    }

    public long iteration() {
        return iteration;
    }

    public void execute() {
        long[] timings = new long[ 6 ];
        long t, start = getNow(), s = start;
        handleCartagoEvents();
        handleAcreEvents();
        handleNotifications();
        timings[ 0 ] = ( t = getNow() ) - s;
        updateBeliefs();
        timings[ 1 ] = ( s = getNow() ) - t;
        senseEnvironment();
        timings[ 2 ] = ( t = getNow() ) - s;

        // System.out.println("Executing before handle event");
        if ( !events.isEmpty() ) {
            boolean eventHandled = handleEvent( event = events.remove() );
            if (runningEvent != null && eventHandled == false && event == runningEvent) {
            	isRunningEvent = false;
            	isEventFailed = true;
            	runningEvent = null;
            }
        }
        // System.out.println("Executing after handle event");

        timings[ 3 ] = ( s = getNow() ) - t;

        List<String> activities = new LinkedList<String>();
        int i = 0;
        while ( i < intentions.size() ) {
            PlanStack planStack = intentions.get( i );
            if ( planStack.isFailed() ) {
                if ( !planStack.rollback() ) {
                    intentions.remove( i );
                    
                    if (runningEvent != null && planStack.label().equals(runningEvent.toString())) {
                    	isRunningEvent = false;
                    	isEventFailed = true;
                    	runningEvent = null;
                    }
                    
                    if (runningPlan != null && planStack.label().equals(runningPlan.toString())) {
                    	isRunningPlan = false;
                    	isPlanFailed = true;
                    	runningPlan = null;
                    }
                    
                    if (runningRule != null && planStack.label().equals(runningRule.event().toString())) {
                    	isRunningRule = false;
                    	isRuleFailed = true;
                    	runningRule = null;
                    }
                }
            } else if ( !planStack.isSuspended() ) {
                IPlanStep step = planStack.getNextActivity();
                if ( step != null ) {
                    activities.add( step.toString() );
                    try {
                        step.handle( this, planStack );
                    }
                    catch ( Throwable th ) {
                        System.out.println( "Problem Executing Step: " + step );
                        System.out.println( "Message: " + th.getMessage() );
                        th.printStackTrace();
                    }
                    if ( planStack.isFailed() ) {
                        intentions.remove( 0 );
                        
                        if (runningEvent != null && planStack.label().equals(runningEvent.toString())) {
                        	isRunningEvent = false;
                        	isEventFailed = true;
                        	runningEvent = null;
                        }
                        
                        if (runningPlan != null && planStack.label().equals(runningPlan.toString())) {
                        	isRunningPlan = false;
                        	isPlanFailed = true;
                        	runningPlan = null;
                        }
                        
                        if (runningRule != null && planStack.label().equals(runningRule.event().toString())) {
                        	isRunningRule = false;
                        	isRuleFailed = true;
                        	runningRule = null;
                        }
                    }
                    i++;
                } else {
                    intentions.remove( i );
                    
                    if (runningEvent != null && planStack.label().equals(runningEvent.toString())) {
                    	isRunningEvent = false;
                    	isEventFailed = false;
                    	runningEvent = null;
                    }
                    
                    if (runningPlan != null && planStack.label().equals(runningPlan.toString())) {
                    	isRunningPlan = false;
                    	isPlanFailed = false;
                    	runningPlan = null;
                    }
                    
                    if (runningRule != null && planStack.label().equals(runningRule.event().toString())) {
                    	isRunningRule = false;
                    	isRuleFailed = false;
                    	runningRule = null;
                    }
                }
            } else {
                i++;
            }
        }

        timings[ 4 ] = ( t = getNow() ) - s;

        synchronized ( activeFunctions ) {
            for ( int j = 0; j < activeFunctions.size(); j++ ) {
                activeFunctions.get( j ).invoke( this );
            }
        }

        timings[ 5 ] = ( s = getNow() ) - t;

        if ( profiler != null )
            profiler.recordTimings( iteration, start, timings, events.size(), beliefs().size(), intentions.size(), activities );
        iteration++;
    }

	private boolean handleEvent( Event event ) {
        for ( ASTRAClass clazz : linearization ) {
            if ( clazz.handleEvent( event, this ) )
                return true;
        }
        return false;
    }

    public List<IFormula> beliefs() {
        List<IFormula> list = new LinkedList<IFormula>();
        for ( List<IFormula> bel : beliefs.values() ) {
            list.addAll( bel );
        }
        return list;
    }

    public List<PlanStack> intentions() {
        return intentions;
    }

    public boolean addEvent( Event event ) {
        if ( eventFilter.contains( event.signature() ) ) {
            events.add( event );
            return true;
        }

        return false;
    }

    public void logAction( String string ) {
        actionLog.add( string );
    }

    public PredicateBuilder getPredicateBuilder( String name2 ) {
        throw new UnsupportedOperationException();
    }

    public LibraryWrapper getLibrary( String moduleId ) {
        return libraries.get( moduleId );
    }

    public void notify( PlanStack planStack, boolean success ) {
        notifications.add( new Notification( planStack, success ) );
    }

    protected void handleNotifications() {
        while ( !notifications.isEmpty() ) {
            Notification notification = notifications.remove();

            notification.planStack.resume();

            if ( !notification.success ) {
                notification.planStack.fail();
            }
        }
    }

    public synchronized boolean hasLock( String token, PlanStack planStack ) {
        return planStack.equals( lockMap.get( token ) );
    }

    public synchronized boolean requestLock( String token, PlanStack planStack ) {
        if ( tokens.contains( token ) ) {
            // No lock, so queue it..
            lockQueueMap.get( token ).addLast( planStack );
            planStack.suspend();
            planStack.back();
            return false;
        }

        tokens.add( token );
        lockQueueMap.put( token, new LinkedList<PlanStack>() );
        lockMap.put( token, planStack );
        return true;
    }

    public synchronized void releaseLock( String token, PlanStack planStack ) {
        if ( !tokens.contains( token ) ) {
            System.err.println( "[" + name() + "] Could not release lock on token: " + token );
        }
        else {
            if ( !lockMap.remove( token ).equals( planStack ) ) {
                System.out.println( "[ALA.releaseLock()] Something strange: look at lock releasing" );
            }

            LinkedList<PlanStack> queue = lockQueueMap.get( token );
            if ( queue.isEmpty() ) {
                tokens.remove( token );
            }
            else {
                PlanStack stack = queue.removeFirst();
                lockMap.put( token, stack );
                stack.resume();
            }

        }
    }

    public List<Bindings> queryAll( IFormula guard ) {
        List<Bindings> bindings = reasoner.queryAll( (IFormula) guard.accept( new EvaluateTermsVisitor( this ) ) );
        return bindings;
    }

    public List<Bindings> query( IFormula guard ) {
        List<Bindings> bindings = reasoner.query( (IFormula) guard.accept( new EvaluateTermsVisitor( this ) ) );
        return bindings;
    }

    public Collection<List<IFormula>> getPercepts() {
        return percepts.values();
    }

    public Collection<List<IFormula>> getBeliefs() {
        return beliefs.values();
    }

    public Queue<Event> getEvents() {
        return this.events;
    }

    public List<String> getActionLog() {
        return actionLog;
    }

    public ASTRAClass getASTRAClass() {
        return linearization.get( 0 );
    }

    public void initialize( IFormula initial ) {
        if ( Predicate.class.isInstance( initial ) ) {
            addBeliefs.add( (Predicate) initial );
        }
    }

    public List<TRFunction> getActiveFunctions() {
        return activeFunctions;
    }

    public void addLibrary( String name, String url ) {
        try {
            LibraryWrapper wrapper = LibraryWrapper.newInstance( url );
            libraries.put( name, wrapper );
            wrapper.setAgent( this );
        }
        catch ( LibraryException e ) {
            e.printStackTrace();
        }
    }

    public Map<String, LibraryWrapper> getLibraries() {
        return libraries;
    }

    public void setLinearization( List<ASTRAClass> linearization ) {
        this.linearization = linearization;

        for ( ASTRAClass clazz : linearization ) {
            events.addAll( clazz.getInitialEvents() );
            eventFilter.addAll( clazz.getEventFilter() );
        }
    }

    public void receive( AstraMessage message ) {
        inbox.add( message );

        // ACRE Integration: make sure incoming message is handled before any events are generated
        if ( conversationManager != null ) {
            ACREMessage m = ACREService.message( message );
            conversationManager.processMessage( m );
        }
        
        events.add( new MessageEvent( message.performative(), Term.newInstance( message.sender() ), message.content() ) );
    }

    public boolean terminate() {
        return terminate;
    }

    public void terminate( boolean terminate ) {
        this.terminate = terminate;
    }

    public void eisService( EISService service ) {
        eisService = service;
    }

    public EISService eisService() {
        return eisService;
    }

    public MessageService messageService() {
        return messageService;
    }

    public void messageService( MessageService messageService ) {
        this.messageService = messageService;
    }

    /**
     * Required by CartagoListener interface as callback for receiving events
     * from Cartago.
     * 
     * @param ev a Cartago event
     */
    @Override
    public synchronized boolean notifyCartagoEvent( CartagoEvent ev ) {
        cartagoEvents.add( ev );
        return false;
    }

    public ICartagoSession getSession() {
        return this.session;
    }

    public void registerOperation( long actId, PlanStack planStack ) {
        operationRegister.put( actId, planStack );
    }

    /**
     * Implementation of handler for processing cartago events...
     */
    protected synchronized void handleCartagoEvents() {
        while ( !cartagoEvents.isEmpty() ) {
            CartagoEvent ev = cartagoEvents.remove( 0 );
            // System.out.println("Processing event: " + ev);
            if ( ev instanceof ActionSucceededEvent ) {
                ActionSucceededEvent evt = (ActionSucceededEvent) ev;

                PlanStack planStack = operationRegister.remove( evt.getActionId() );
                ( (ICartagoPlanStep) planStack.getCurrent() ).setOperation( evt.getOp() );
                notify( planStack, true );

                if ( ev instanceof FocusSucceededEvent ) {
                    FocusSucceededEvent ev1 = (FocusSucceededEvent) ev;
                    for ( ArtifactObsProperty prop : ev1.getObsProperties() ) {
                        if ( ev1.getArtifactId() != null ) {
                            System.out.println( "cartago property event artifact id is NOT null!!" );
                            System.exit( 0 );
                        }

                        List<ITerm> list = new LinkedList<ITerm>();
                        for ( Object obj : prop.getValues() ) {
                            list.add( Term.newInstance( obj ) );
                        }

                        Predicate property = new Predicate( prop.getName(), list.toArray( new ITerm[ list.size() ] ) );
                        artifactStore.storeObservableProperty( prop.getFullId(), property );

                        addEvent( new CartagoPropertyEvent( CartagoPropertyEvent.ADDED, Term.newInstance( prop.getFullId() ), property ) );
                    }
                }
                else if ( ev instanceof StopFocusSucceededEvent ) {
                    StopFocusSucceededEvent ev1 = (StopFocusSucceededEvent) ev;
                    for ( ArtifactObsProperty prop : ev1.getObsProperties() ) {
                        artifactStore.removeObservableProperty( prop.getFullId() );

                        // Generate the event...
                        List<ITerm> list = new LinkedList<ITerm>();
                        for ( Object obj : prop.getValues() ) {
                            list.add( Term.newInstance( obj ) );
                        }

                        addEvent( new CartagoPropertyEvent( CartagoPropertyEvent.REMOVED, Term.newInstance( prop.getFullId() ), new Predicate( prop.getName(), list.toArray( new ITerm[ list.size() ] ) ) ) );
                    }
                }
                else if ( ev instanceof JoinWSPSucceededEvent ) {
//                    JoinWSPSucceededEvent ev1 = (JoinWSPSucceededEvent) ev;
//                    addBelief( new Predicate( "joinedWorkspace", new ITerm[] { Term.newInstance(ev1.getWorkspaceId()) } ) );
                }
            }
            else if ( ev instanceof ActionFailedEvent ) {
                ActionFailedEvent evt = (ActionFailedEvent) ev;
                PlanStack planStack = operationRegister.remove( evt.getActionId() );
                ( (ICartagoPlanStep) planStack.getCurrent() ).setOperation( evt.getOp() );
                notify( planStack, false );
                
                Tuple signal = evt.getFailureDescr();
                
                // Convert the signal from the artifact to an event in the
                // event queue...
                List<ITerm> terms = new LinkedList<ITerm>();
                for ( Object obj : signal.getContents() ) {
                    terms.add( Term.newInstance( obj ) );
                }

                addEvent( new MentalEvent( MentalEvent.ADDITION, new Predicate( signal.getLabel(), terms.toArray( new ITerm[ terms.size() ] ) ) ) );
                System.out.println( "[" + name + "] CARTAGO Action failed: " + planStack.getCurrent() + ": " + evt.getFailureMsg() );
                this.logAction( planStack.getCurrent().toString( "" ) + " [" + evt.getFailureMsg() + "]" );
            }
            else if ( ev instanceof FocussedArtifactDisposedEvent ) {
                FocussedArtifactDisposedEvent ev1 = (FocussedArtifactDisposedEvent) ev;
                for ( ArtifactObsProperty prop : ev1.getObsProperties() ) {
                    artifactStore.removeObservableProperty( prop.getFullId() );

                    // Generate the event...
                    List<ITerm> list = new LinkedList<ITerm>();
                    for ( Object obj : prop.getValues() ) {
                        list.add( Term.newInstance( obj ) );
                    }

                    addEvent( new CartagoPropertyEvent( CartagoPropertyEvent.REMOVED, Term.newInstance( prop.getFullId() ), new Predicate( prop.getName(), list.toArray( new ITerm[ list.size() ] ) ) ) );
                }
            }
            else if ( ev instanceof ArtifactObsEvent ) {
                ArtifactObsEvent evt = (ArtifactObsEvent) ev;

                // NOTE: Here the event's getArtifactId(..) method returns an id
                Tuple signal = evt.getSignal();
                if ( signal != null ) {
                    // Convert the signal from the artifact to an event in the
                    // event queue...
                    List<ITerm> terms = new LinkedList<ITerm>();
                    for ( Object obj : signal.getContents() ) {
                        terms.add( Term.newInstance( obj ) );
                    }

                    addEvent( new MentalEvent( MentalEvent.ADDITION, new Predicate( signal.getLabel(), terms.toArray( new ITerm[ terms.size() ] ) ) ) );
                }

                if ( evt.getAddedProperties() != null ) {
                    for ( ArtifactObsProperty prop : evt.getAddedProperties() ) {
                        if ( evt.getArtifactId() != null ) {
                            // System.out.println("cartago property event artifact id is NOT null!!");
                            // System.exit(0);
                        }

                        List<ITerm> list = new LinkedList<ITerm>();
                        for ( Object obj : prop.getValues() ) {
                            list.add( Term.newInstance( obj ) );
                        }

                        Predicate property = new Predicate( prop.getName(), list.toArray( new ITerm[ list.size() ] ) );
                        artifactStore.storeObservableProperty( prop.getFullId(), property );

                        addEvent( new CartagoPropertyEvent( CartagoPropertyEvent.ADDED, Term.newInstance( prop.getFullId() ), property ) );
                    }
                }

                if ( evt.getRemovedProperties() != null ) {
                    for ( ArtifactObsProperty prop : evt.getRemovedProperties() ) {
                        artifactStore.removeObservableProperty( prop.getFullId() );

                        if ( evt.getArtifactId() != null ) {
                            // System.out.println("cartago property event artifact id is NOT null!!");
                            // System.exit(0);
                        }

                        List<ITerm> list = new LinkedList<ITerm>();
                        for ( Object obj : prop.getValues() ) {
                            list.add( Term.newInstance( obj ) );
                        }

                        Predicate property = new Predicate( prop.getName(), list.toArray( new ITerm[ list.size() ] ) );

                        addEvent( new CartagoPropertyEvent( CartagoPropertyEvent.REMOVED, Term.newInstance( prop.getFullId() ), property ) );
                    }
                }

                if ( evt.getChangedProperties() != null ) {
                    for ( ArtifactObsProperty prop : evt.getChangedProperties() ) {
                        if ( evt.getArtifactId() != null ) {
                            // System.out.println("cartago property event artifact id is NOT null!!");
                            // System.exit(0);
                        }

                        List<ITerm> list = new LinkedList<ITerm>();
                        for ( Object obj : prop.getValues() ) {
                            list.add( Term.newInstance( obj ) );
                        }

                        Predicate property = new Predicate( prop.getName(), list.toArray( new ITerm[ list.size() ] ) );
                        artifactStore.storeObservableProperty( prop.getFullId(), property );

                        addEvent( new CartagoPropertyEvent( CartagoPropertyEvent.UPDATED, Term.newInstance( prop.getFullId() ), property ) );
                    }
                }
            }
            else if ( ev instanceof QuitWSPSucceededEvent ) {
//                try {
//                    currentWorkspace = getObjectId( session.getCurrentWorkspace() );
//                }
//                catch ( CartagoException e ) {
//                    // TODO Auto-generated catch block
//                    e.printStackTrace();
//                }
            }
            else {
                System.out.println( "unhandled event: " + ev );
            }
        }
    }
    
    @Override
    public void update( Observable o, Object e ) {
    	acreEvents.add((ACREEvent) e);
    }
    
    private void handleAcreEvents() {
    	while (!acreEvents.isEmpty()) {
    		ACREEvent e = acreEvents.remove(0);
            if ( e instanceof IConversationEvent ) {
                Conversation c = ( (IConversationEvent) e ).getConversation();
                if ( e instanceof ConversationStartedEvent ) {
                    // @acre(started, string cid)
                    addEvent( new ACREASTRAEvent( ACREASTRAEvent.STARTED, Term.newInstance( c.getConversationIdentifier() ) ) );
                }
                else if ( e instanceof ConversationEndedEvent ) {
                    // @acre(ended, string cid)
                    addEvent( new ACREASTRAEvent( ACREASTRAEvent.ENDED, Term.newInstance( c.getConversationIdentifier() ) ) );
                }
                else if ( e instanceof ConversationFailedEvent ) {
                    // @acre(failed, string cid)
                    addEvent( new ACREASTRAEvent( ACREASTRAEvent.FAILED, Term.newInstance( c.getConversationIdentifier() ) ) );
                }
                else if ( e instanceof ConversationCancelRequestEvent ) {
                    // @acre(cancel_request, string cid)
                    addEvent( new ACREASTRAEvent( ACREASTRAEvent.CANCEL_REQUEST, Term.newInstance( c.getConversationIdentifier() ) ) );
                }
                else if ( e instanceof ConversationCancelFailEvent ) {
                    // @acre(cancel_fail, string cid)
                    addEvent( new ACREASTRAEvent( ACREASTRAEvent.CANCEL_FAIL, Term.newInstance( c.getConversationIdentifier() ) ) );
                }
                else if ( e instanceof ConversationCancelConfirmEvent ) {
                    // @acre(cancel_confirm, string cid)
                    addEvent( new ACREASTRAEvent( ACREASTRAEvent.CANCEL_CONFIRM, Term.newInstance( c.getConversationIdentifier() ) ) );
                }
                else if ( e instanceof ConversationTimeoutEvent ) {
                    // @acre(timeout, string cid)
                    addEvent( new ACREASTRAEvent( ACREASTRAEvent.TIMEOUT, Term.newInstance( c.getConversationIdentifier() ) ) );
                }
                else if (e instanceof ConversationAdvancedEvent) {
                    // @acre(advanced, string cid, string state, int length)
                	ConversationAdvancedEvent event = (ConversationAdvancedEvent) e;
                	addEvent(new ACREASTRAEvent(ACREASTRAEvent.ADVANCED,
    						Term.newInstance(c.getConversationIdentifier()),
    						Term.newInstance(event.getState().getName()),
    						Term.newInstance(event.getLength())
                	      ));

    				   // @acre(message, string cid, performative, predicate content)
    				   IACREMessage m = c.getHistory().get( event.getLength() - 1 );
    				   addEvent(new ACREASTRAEvent( ACREASTRAEvent.MESSAGE,
    				         Term.newInstance( c.getConversationIdentifier() ),
    				         m.getPerformative(),
    				         ACREService.toPredicate( m.getContent() )));
                } 
            }
            else if (e instanceof UnmatchedMessageEvent) {
               // @acre(unmatched, string description)
               UnmatchedMessageEvent event = (UnmatchedMessageEvent) e;
               IACREMessage m = event.getMessage();
               
               String details = "performative=["+m.getPerformative().toLowerCase() +
                     "],sender=["+m.getSender().getName() +
                     "],content=["+m.getContent() + "]";
               
               if ( m.getProtocol() != null ) {
                  details += ",protocol=[" + m.getProtocol().getUniqueID() + "]";
               }
               if ( m.getConversationIdentifier() != null ) {
                  details += ",conversation=[" + m.getConversationIdentifier() + "]";
               }
             
               addEvent(new ACREASTRAEvent(ACREASTRAEvent.UNMATCHED,
                     Term.newInstance( details )
                     ));
          
            } else if (e instanceof AmbiguousMessageEvent) {
               // @acre(ambiguous, string description)
               AmbiguousMessageEvent event = (AmbiguousMessageEvent) e;
               IACREMessage m = event.getMessage();
             
               addEvent(new ACREASTRAEvent(ACREASTRAEvent.AMBIGUOUS,
                     Term.newInstance( "performative=["+m.getPerformative().toLowerCase() +
                           "],sender=["+m.getSender().getName() +
                           "],content=["+m.getContent()+"]" )
                     ));
            }
    	   }
    }

    public IACREAgentIdentifier getAcreIdentifier() {
        return acreId;
    }

    public ConversationManager getConversationManager() {
        return conversationManager;
    }

    private void senseACRE(List<Predicate> newPerceptList) {
        Map<String, Conversation> conversations = conversationManager.getAllConversations();
        for ( Conversation c : conversations.values() ) {

            String cid = c.getConversationIdentifier();
            ProtocolDescriptor pid = c.getProtocol().getDescriptor();
            IACREAgentIdentifier participant = conversationManager.getOtherParticipant( c );
            if ( !c.isArchived() ) {

            	addPerception( newPerceptList, new Predicate( "conversationStatus", new ITerm[] { Term.newInstance( cid ), Term.newInstance( c.getStatus().toString() ) } ) );

            	addPerception( newPerceptList, new Predicate( "conversationParticipant", new ITerm[] { Term.newInstance( cid ), Term.newInstance( participant.getName() ) } ) );
            	addPerception( newPerceptList, new Predicate( "conversationProtocolID", new ITerm[] { Term.newInstance( cid ), Term.newInstance( pid.getUniqueID() ) } ) );
            	addPerception( newPerceptList, new Predicate( "conversationProtocolName", new ITerm[] { Term.newInstance( cid ), Term.newInstance( pid.getName() ) } ) );
            	addPerception( newPerceptList, new Predicate( "conversationProtocolNamespace", new ITerm[] { Term.newInstance( cid ), Term.newInstance( pid.getNamespace() ) } ) );
            	addPerception( newPerceptList, new Predicate( "conversationProtocolVersion", new ITerm[] { Term.newInstance( cid ), Term.newInstance( pid.getVersion() ) } ) );

                if ( c.getStatus() == ConversationStatus.ACTIVE || c.getStatus() == ConversationStatus.FINISHED ) {
                	addPerception( newPerceptList, new Predicate( "conversationState", new ITerm[] { Term.newInstance( cid ), Term.newInstance( c.getCurrentState().getName() ) } ) );

                	addPerception( newPerceptList, new Predicate( "conversationLength", new ITerm[] { Term.newInstance( cid ), Term.newInstance( c.getLength() ) } ) );
                }
            }
            else {
            	addPerception( newPerceptList, new Predicate( "conversationArchived", new ITerm[] { Term.newInstance( cid ), Term.newInstance( pid.toString() ), Term.newInstance( participant.getName() ) } ) );
            }
        }
        // protocol knowledge
        for ( Protocol p : ACREService.manager.getProtocols() ) {
        	addPerception( newPerceptList, new Predicate( "knownProtocol", new ITerm[] { Term.newInstance( p.getDescriptor().getUniqueID() ) } ) );
        }

        // remove finished protocols from the active list
        conversationManager.removeFinished();
        conversationManager.doTimeouts();
    }

	/**
	 * Retrieve a message from a conversations history based on the id of
	 * the conversation and the index of the message to be returned. The
	 * index starts at 1.
	 * 
	 * @param cid the conversation identifier
	 * @param index the index of the message to be returned.
	 * @return
	 */
    public IACREMessage getAcreMessage(String cid, int index) {
		Conversation con = conversationManager.getConversationByID(cid);
		if (con == null) return null;
		List<IACREMessage> history = con.getHistory();
		if (index <= history.size() && index >= 1) {
			return history.get(index-1);
		}
		return null;
	}

    /**
     * Returns the agents artifact store, which manages information on observable
     * properties etc.
     * 
     * @return the agents artifact store.
     */
	public ArtifactStore getArtifactStore() {
		return artifactStore;
	}
	
	public Event currentEvent() {
		return event;
	}
	

	
	// Unit Testing Methods
	public List<IFormula> getCurrentBeliefs() {
		
		Map<String, List<IFormula>> currentBeliefs = new HashMap<String, List<IFormula>>(beliefs);
		
		for (Map.Entry<String, List<IFormula>> belief : beliefs.entrySet()) {
		    String sig = belief.getKey();
		    LinkedList<IFormula> value = (LinkedList<IFormula>) belief.getValue();
		    
		    currentBeliefs.put(sig, (List<IFormula>) value.clone());
		}
		
		for (Predicate belief : addBeliefs) {
			String sig = belief.signature();
            List<IFormula> list = currentBeliefs.get( sig );
            if ( list == null ) {
                list = new LinkedList<IFormula>();
                currentBeliefs.put( sig, list );
            }
            if ( !list.contains( belief ) ) {
                list.add( belief );
            }
		}

		for (Predicate belief : dropBeliefs) {
			String sig = belief.signature();
            List<IFormula> list = currentBeliefs.get( sig );
            if ( list != null ) {
                if ( list.remove( belief ) ) {
                	
                }
            }
		}
		
        List<IFormula> list = new LinkedList<IFormula>();
        for ( List<IFormula> bel : currentBeliefs.values() ) {
            list.addAll( bel );
        }
        return list;
	}
	
	public boolean verifyBelief(IFormula belief) {
		List<IFormula> currentBeliefs = this.getCurrentBeliefs();
		
		for (IFormula currentBelief : currentBeliefs) {
			if (((Predicate)currentBelief).signature().equals(((Predicate)belief).signature())) {
				return true;
			}
		}
		return false;
	}
	
	public boolean verifyBeliefs(List<IFormula> beliefs) {
		List<IFormula> currentBeliefs = this.getCurrentBeliefs();
		
		for (IFormula belief : currentBeliefs) {
			boolean finded = false;
			for (IFormula currentBelief : currentBeliefs) {
				if (((Predicate)currentBelief).signature().equals(((Predicate)belief).signature())) {
					finded = true;
					break;
				}
			}
			if (finded == false) {
				return false;
			}
		}
		return true;
	}
	
	public boolean verifyEvent(Event event) {
        for ( ASTRAClass clazz : linearization ) {
            if ( clazz.verifyEvent( event, this ) )
                return true;
        }
        return false;
	}
	
	public boolean runEvent(MentalEvent event) {
		if (!this.addEvent(event)) {
			return false;
		}
		this.runningEvent = event;
		this.isEventFailed = false;
		this.isRunningEvent = true;
		while(this.isRunningEvent) {
			this.execute();
		}
		return !isEventFailed;
	}
	
	public boolean verifyPlan(Predicate predicate) {
        for ( ASTRAClass clazz : linearization ) {
            if (clazz.getPlan(predicate) != null) {
                return true;
            }
        }
        return false;
	}
	
	public boolean runPlan(Predicate predicate) {
		boolean isVerified = false;
        for ( ASTRAClass clazz : linearization ) {
            if (clazz.getPlan(predicate) != null) {
            	clazz.addIntention(predicate, this);
            	isVerified = true;
            	
        		this.runningPlan = predicate.toString();
        		this.isPlanFailed = false;
        		this.isRunningPlan = true;
                break;
            }
        }
        if (!isVerified) {
        	return false;
        } else {
    		while(this.isRunningPlan) {
    			this.execute();
    		}
        }
        
		return !isPlanFailed;
	}
	
	
	public boolean verifyRule(Rule rule) {
        for ( ASTRAClass clazz : linearization ) {
            if ( clazz.verifyRule(rule) )
                return true;
        }
        return false;
	}
	
	public boolean runRule(Rule rule) {
		this.runningRule = rule;
		this.isRuleFailed = false;
		this.isRunningRule = true;

    	boolean ruleHandled = handleRule(runningRule);
        if (ruleHandled == false) {
        	isRunningRule = false;
        	isRuleFailed = true;
        	runningRule = null;
        }
		while(this.isRunningRule) {
			this.execute();
		}
		return !isRuleFailed;
	}
	
    private boolean handleRule(Rule rule) {
        for ( ASTRAClass clazz : linearization ) {
            if ( clazz.handleRule( rule, this ) )
                return true;
        }
        return false;
	}
	
	public boolean runGoal(MentalEvent event) {
		return false;
	}
}
